/*
 *  client.c
 *  
 *
 *  Created by Viktor Kurz & Stefan Wrobel on 07.12.09.
 *  Copyright 2009. All rights reserved.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <signal.h>
#include <netdb.h>
#include <curses.h>

#include "chat.h"

#define MSG_BUF_SIZE	100

/* GUI defines */
#define GUI_LOGIN			1
#define GUI_START_CHAT		2
#define GUI_KEYPRESSED		3
#define GUI_MESSAGE			4
#define GUI_NAMELIST		5

void gui(int);
void finisher(int);

/* Gloabele Daten, damit GUI sich selbst die Daten holen kann. Und finisher die Abmeldenachricht schickt */
int sock;												/* Socket-Deskriptor */
struct sockaddr_in server_addr;							/* Server-Socket */
struct chat_message msg_buffer[MSG_BUF_SIZE];			
struct chat_message message;							/* Chat-Nachricht */
char input_buffer[sizeof(message.data)];				/* Eingabe-Buffer */
char nameliste[sizeof(message.data)];
unsigned int msg_buf_pos = 0;
unsigned int num_of_messages = 0;


int main (int argc, const char * argv[]) {
	
	signal(SIGINT, finisher);
	signal(SIGWINCH,gui);									/* hört auf Fenstergrößeveränderung */
	int len;
	int inputkey;
	int loopvar1;
	unsigned int source_addr_len = sizeof(struct sockaddr);
	fd_set my_readset;										/* Anlegen der Datenstruktur für select */
	struct hostent *host;

	if (argc < 3)
    {
        fprintf(stderr, "usage: client <host> <port> <nickname>\n");
        return 1;
    }
	
	/* server_addr.sin_addr argv1 zuweisen, falls keine IP auf Domain testen und zuweisen sonst Fehler*/
    if (!inet_aton(argv[1], &server_addr.sin_addr))
    {
        host = gethostbyname(argv[1]);
        if (!host)
        {
            herror("gethostbyname() failed");
            return 1;
        }
		server_addr.sin_addr = *(struct in_addr*)host->h_addr;
    }	
	
	/* Server IP/Port */
	server_addr.sin_family = AF_INET;
	server_addr.sin_port   = htons(atoi(argv[2]));
	
	if ((sock = socket( AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("fehler bei socket");
		return 1;
	}

	/* REGISTER-Nachricht an den Server schicken */
	message.type = REGISTER;
	strcpy(message.data,argv[3]);
	if((len=sendto (sock, &message, strlen(message.data)+1 + sizeof(message.type), 0,(struct sockaddr*) &server_addr, sizeof(struct sockaddr_in))) == -1){
		perror("Fehler bei Sendto!");
		return 1;
	}
	
	gui(GUI_START_CHAT);
	while (1) {
		
		FD_ZERO (&my_readset); 
		FD_SET (0, &my_readset); 
		FD_SET (sock, &my_readset);
		
		select (sock+1, &my_readset,NULL, NULL, NULL);
		
		if (FD_ISSET(0, &my_readset) ) {
			inputkey =  getch();								/* Von Tastatur lesen */
			if(inputkey==13) {									/* Enter */
				if(strlen(input_buffer)>0){						
					strcpy(message.data,input_buffer);			/* Eingabe-Buffer in die Nachricht kopieren */
					input_buffer[0] = '\0';						/* Eingabe-Buffer leer machen */
					message.type = MESSAGE;
					if((len=sendto (sock, &message, strlen(message.data)+1 + sizeof(message.type), 0,(struct sockaddr*) &server_addr, sizeof(struct sockaddr_in))) == -1){
						perror("Fehler bei Sendto!");
						return 1;
					}
				}
			} else if(inputkey==127) {									/* Backspace */
				if(strlen(input_buffer)>0){
					input_buffer[strlen(input_buffer)-1]='\0';			/* Letztes Zeichen löschen */
					
				}
			} else if(inputkey>31 && inputkey<127){						/* Druckbare Zeichen */
				sprintf(input_buffer,"%s%c",input_buffer,inputkey);		/* TODO bufferueberlauf!! */
			}
			gui(GUI_KEYPRESSED);										/* GUI mitteilen das eine Taste gedrückt wurde */
		} else if (FD_ISSET(sock, &my_readset)) {
			if((len = recvfrom(sock, &message, sizeof(message), 0, (struct sockaddr*) &server_addr, &source_addr_len)) == -1) {
				perror("Fehler bei Recvfrom!");
				return 1;
			}
			switch (message.type) {
				case MESSAGE:
					msg_buffer[msg_buf_pos].data[0] = '\0';				/* lösche älteste Nachricht im MSG_Buffer */
					strcpy(msg_buffer[msg_buf_pos].data, message.data);	/* kopiere erhaltene Nachticht in den MSG_Buffer */
					msg_buffer[msg_buf_pos].type = MESSAGE;				/* Type der neuen Nachricht auf MESSAGE setzen */
					msg_buf_pos++;										/* msg_buf_pos inkrementieren */
					msg_buf_pos = msg_buf_pos % MSG_BUF_SIZE;			/* falls Zeiger über den Buffer zeigt auf 0 setzen */
					if(num_of_messages < MSG_BUF_SIZE) {				
						num_of_messages++;								/* Anzahl der Nachrichten um Eins erhöhen falls der Buffer nicht schon voll ist */
					}
					gui(GUI_MESSAGE);									/* GUI mitteilen das eine Nachricht rein kam */
					break;
				case NAMELIST:
					strcpy(nameliste,(message.data+1));					/* erstes Komma löschen und in nameliste speichern */
					/* Komma durch Carrig Return ersetzen */
					for (loopvar1=0;loopvar1<strlen(nameliste);loopvar1++) {
						if(nameliste[loopvar1]==',') nameliste[loopvar1] = '\n';
					}
					gui(GUI_NAMELIST);									/* GUI mitteilen das eine Namelist rein kam */
					break;
				default:
					break;
			}
		} else {
			fprintf(stderr, "fehler bei select: sollte nie auftreten");
			return 1;
		}
	}
	return 0;
}

void finisher(int sig){
	/* Fenster von ncurses beenden */
	endwin();
	
	/* UNREGISTER-Nachricht an den Server schicken */
	message.type = UNREGISTER;
	if((sendto (sock, &message, strlen(message.data)+1 + sizeof(message.type), 0,(struct sockaddr*) &server_addr, sizeof(struct sockaddr_in))) == -1){
		perror("Fehler bei Sendto!");
		exit(1);
	}
	exit(0);
}

void gui(int changetype){
	
	/* Fenster erzeugen */
	static WINDOW *win_namelist;
	static WINDOW *win_message;
	static WINDOW *win_input;
	
	/* Fenster für die Rahmen erzeugen */
	WINDOW *win_namelist_boarder;
	WINDOW *win_message_boarder;
	WINDOW *win_input_boarder;
	
	int x;											/* stdscr Höhe */
	int y;											/* stdscr Breite */
	int loopvar1;
	static int x_message;							/* win_message Höhe */
	static int y_message;							/* win_message Breite */
	int pos_in_win = 0;								/* berechnete Position einer Nachricht im message-window */
	int mehrzeiler = 0;								/* Anzahl der Mehrzeiler */
	
	switch (changetype) {
			
		case GUI_MESSAGE:
			wclear(win_message);
			/* Message-Fenster von unten auf mit Nachrichten befüllen */
			for(loopvar1 = 0;  loopvar1 < num_of_messages; loopvar1++) {
				mehrzeiler=mehrzeiler+((strlen(msg_buffer[((msg_buf_pos-loopvar1-1)%MSG_BUF_SIZE)].data)/y_message));
				pos_in_win=x_message-loopvar1-mehrzeiler-1;
				mvwprintw(win_message, pos_in_win, 0,"%s", msg_buffer[((msg_buf_pos-loopvar1-1)%MSG_BUF_SIZE)].data);
			}
			wrefresh(win_message);								/* Message-Fenster erneuern */
			wrefresh(win_input);								/* Ins Input-Fenster springen */
			break;
			
		case GUI_NAMELIST:
			wclear(win_namelist);
			mvwprintw(win_namelist, 0, 0,"%s", nameliste);
			wrefresh(win_namelist);								/* Namelist-Fenster erneuern */
			wrefresh(win_input);								/* Ins Input-Fenster springen */
			break;
			
		case GUI_KEYPRESSED:
			mvwprintw(win_input, 0, 0,"\n\n\n\n");				/* Input Fenster ohne Flakern leer machen */
			mvwprintw(win_input, 0, 0,"%s", input_buffer);		
			wrefresh(win_input);								/* Ins Input-Fenster springen */
			break;
			
		case GUI_START_CHAT:
			/* Curses initialisieren */
			initscr();
			cbreak();									/* Zeilenbuffer ausschalten */
			noecho();									/* Ausgabe der Eingabe ausschalten */
			nonl();										/* Control Zeichen aus */
			intrflush(stdscr, FALSE);
			keypad(stdscr, TRUE);	
			
			/* Fenstergröße von stdscr abfragen*/
			getmaxyx(stdscr,x,y);						
			
			/* Fenster initialisieren */
			win_input = newwin(3, y-22, x-4, 21);
			win_namelist = newwin(x-2, 18, 1, 1);
			win_message = newwin(x-7, y-22, 1, 21);
			win_input_boarder = newwin(5, y-20, x-5, 20);
			win_namelist_boarder = newwin(x, 20, 0, 0);
			win_message_boarder = newwin(x-5, y-20, 0, 20);
			
			/* Rahmen um die Fenster zeichenen */
			box(win_message_boarder, 0, 0);
			box(win_namelist_boarder, 0, 0);
			box(win_input_boarder, 0, 0);
			
			/* Zeichen lassen */
			refresh();
			wrefresh(win_message_boarder);
			wrefresh(win_namelist_boarder);
			wrefresh(win_input_boarder);
			wrefresh(win_message);
			wrefresh(win_namelist);
			wrefresh(win_input);
			
			/* Fenstergröße von win_message abfragen */
			getmaxyx(win_message,x_message,y_message);			
			break;
			
		default:
			/* Falls die Größe des Terminal verändert wird */
			endwin();
			gui(GUI_START_CHAT);
			gui(GUI_MESSAGE);
			gui(GUI_NAMELIST);
	}
}